package edu.bupt.changelog.history;

import edu.bupt.changelog.history.anno.HistoryGet;
import edu.bupt.changelog.history.anno.HistoryUniqueKey;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class HistoryInitializer implements InstantiationAwareBeanPostProcessor,ApplicationContextAware, ApplicationListener<ApplicationStartedEvent> {

    private static Map<String,GetInfo> map = new ConcurrentHashMap<>();

    private static class GetInfo{
        private String beanName;
        private Class entityClass;
        private String methodName;
        private Class[] methodParamTypes;
        private List<String> primaryKeys=new ArrayList<>();

        public String getBeanName() {
            return beanName;
        }

        public void setBeanName(String beanName) {
            this.beanName = beanName;
        }

        public Class getEntityClass() {
            return entityClass;
        }

        public void setEntityClass(Class entityClass) {
            this.entityClass = entityClass;
        }

        public String getMethodName() {
            return methodName;
        }

        public void setMethodName(String methodName) {
            this.methodName = methodName;
        }

        public Class[] getMethodParamTypes() {
            return methodParamTypes;
        }

        public void setMethodParamTypes(Class[] methodParamTypes) {
            this.methodParamTypes = methodParamTypes;
        }

        public List<String> getPrimaryKeys() {
            return primaryKeys;
        }

        public void setPrimaryKeys(List<String> primaryKeys) {
            this.primaryKeys = primaryKeys;
        }
    }


    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        Method[] declaredMethods = beanClass.getDeclaredMethods();

        for (Method method : declaredMethods){
            HistoryGet annotation = method.getAnnotation(HistoryGet.class);
            if(annotation != null){
                String name = method.getName();
                Class<?>[] parameterTypes = method.getParameterTypes();
                GetInfo getInfo = new GetInfo();
                getInfo.setBeanName(beanName);
                getInfo.setMethodName(name);
                getInfo.setMethodParamTypes(parameterTypes);
                getInfo.setEntityClass(annotation.value().getEntityClass());
                map.put(beanName,getInfo);


                Class entityClass = annotation.value().getEntityClass();
                List<String> keys = new ArrayList<>();
                Field[] fields = entityClass.getDeclaredFields();
                for(Field field:fields){
                    HistoryUniqueKey unikeyAnno = field.getAnnotation(HistoryUniqueKey.class);
                    if(unikeyAnno != null){
                        keys.add(field.getName());
                    }
                }

                getInfo.setPrimaryKeys(keys);
            }
        }
        return null;
    }

    private ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        // 从delegatedBean中拿到方法;
        for(String beanName : map.keySet()){
            GetInfo getInfo = map.get(beanName);
            Object delegatedBean = applicationContext.getBean(beanName);
            try {
                Method delegatedMethod = delegatedBean.getClass().getMethod(getInfo.getMethodName(), getInfo.getMethodParamTypes());

                HistoryRegistion.HistoryRegistInfo info = new HistoryRegistion.HistoryRegistInfo();
                info.setRepositoryInstance(delegatedBean);
                info.setRepositoryGetMethod(delegatedMethod);
                info.setPrimaryKeys(getInfo.getPrimaryKeys());

                HistoryRegistion.tryPut(getInfo.getEntityClass(),info);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }

        }

    }
}
